/*
 * Copyright 2000-2011 Worth Enterprises, Inc.  All Rights Reserved.
 */
package com.worthent.foundation.util.state.provider;

import java.util.LinkedList;

import com.worthent.foundation.util.state.StateDef;
import com.worthent.foundation.util.state.StateDefException;
import com.worthent.foundation.util.state.StateEvent;
import com.worthent.foundation.util.state.StateExeException;
import com.worthent.foundation.util.state.StateTable;
import com.worthent.foundation.util.state.StateTableData;
import com.worthent.foundation.util.state.StateTableDef;
import com.worthent.foundation.util.state.StateTransitionDef;
import com.worthent.foundation.util.state.TransitionActor;

/**
 * Provides an in-memory implementation of the state table instance that
 * supports state table stacking. Stacked state tables are often useful for
 * supporting recursive event processing such as parsing a recursive data
 * structure.
 * 
 * @author Erik K. Worth
 * @version $Id: InMemoryStackableStateTable.java 309 2009-10-01 02:25:57Z
 *          EWorth $
 */
public class InMemoryStackableStateTable extends InMemoryStateTable implements
    StackableHistoryInstance {

    /** The name of the push event */
    public static final String PUSH_HISTORY_EVENT_NAME = "PushHistoryEvent";

    /** The name of the pop event */
    public static final String POP_HISTORY_EVENT_NAME = "PopHistoryEvent";

    /** The name of the actor that pushes history onto the stack */
    private static final String PUSH_HISTORY_ACTOR_NAME = "Push History Actor";

    /** The name of the actor that pops history off the stack */
    private static final String POP_HISTORY_ACTOR_NAME = "Pop History Actor";

    /** Transition Actor class that pushes the history onto the stack */
    private static final class PushHistoryTransitionActor implements
        TransitionActor {
        /*
         * (non-Javadoc)
         * 
         * @see
         * com.worthent.foundation.service.spi.util.state.TransitionActor#getName
         * ()
         */
        @Override
        public String getName() {
            return PUSH_HISTORY_ACTOR_NAME;
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * com.worthent.foundation.service.spi.util.state.TransitionActor#onAction
         * (java.lang.String, java.lang.String,
         * com.worthent.foundation.service.spi.util.state.StateTableInstance,
         * com.worthent.foundation.service.spi.util.state.StateEvent)
         */
        @Override
        public void onAction(
            final String fromState,
            final String toState,
            final StateTable table,
            final StateEvent event) throws StateExeException {

            // The state table instance must implement StackableHistoryInstance
            final StackableHistoryInstance instance =
                (StackableHistoryInstance) table;

            // Preserve the current state table history to be restored later
            // by the pop
            instance.pushStateHistory(event);
        }

        /** Hide Constructor */
        private PushHistoryTransitionActor() {
        }
    } // PushHistoryTransitionActor

    /** Transition Actor class that pops the history off the stack */
    private static final class PopHistoryTransitionActor implements
        TransitionActor {

        /*
         * (non-Javadoc)
         * 
         * @see
         * com.worthent.foundation.service.spi.util.state.TransitionActor#getName
         * ()
         */
        @Override
        public String getName() {
            return POP_HISTORY_ACTOR_NAME;
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * com.worthent.foundation.service.spi.util.state.TransitionActor#onAction
         * (java.lang.String, java.lang.String,
         * com.worthent.foundation.service.spi.util.state.StateTableInstance,
         * com.worthent.foundation.service.spi.util.state.StateEvent)
         */
        @Override
        public void onAction(
            final String fromState,
            final String toState,
            final StateTable table,
            final StateEvent event) throws StateExeException {

            // The state table instance must implement StackableHistoryInstance
            final StackableHistoryInstance instance =
                (StackableHistoryInstance) table;

            // Restore the state table to the point where it was pushed.
            instance.popStateHistory(event);
        }

        /** Hide constructor */
        private PopHistoryTransitionActor() {
        }
    }

    /** Transition Actor that pushes the history onto the stack */
    public static final TransitionActor PUSH_HISTORY =
        new PushHistoryTransitionActor();

    /**
     * Transition Actor that restores history from the history popped off the
     * stack
     */
    public static final TransitionActor POP_HISTORY =
        new PopHistoryTransitionActor();

    /** Transition definition that restores the state instance history */
    public static final StateTransitionDef POP_HISTORY_DEF =
        new StateTransitionDef(
            POP_HISTORY_EVENT_NAME,
            StateDef.STATE_CHANGE_BY_ACTOR,
            POP_HISTORY);

    /** History stack used to store pushed histories */
    private final LinkedList<StateTableData> stack;

    /** Constructs the state table instance with the table definition */
    public InMemoryStackableStateTable(final StateTableDef stateTblDef)
        throws StateDefException {
        super(stateTblDef);
        stack = new LinkedList<StateTableData>();
    }

    /** Pushes the specified state history onto the stack */
    protected final void pushStateHistory(final StateTableData history) {
        stack.addFirst(history);
    }

    /** Pops the top-most history off the stack and returns it. */
    protected final StateTableData popStateHistory() throws StateExeException {
        if (stack.isEmpty()) {
            throw new StateExeException(
                "Attempt to pop history off of an empty stack");
        }
        return stack.removeFirst();
    }

    //
    // StackableHistoryInstance
    //

    /**
     * Pushes a copy of the current <code>StateHistory</code> onto the stack so
     * that it can later be restored via the {@link #popStateHistory} method.
     * 
     * @param event unused
     */
    public void pushStateHistory(final StateEvent event) {
        pushStateHistory(event.getStateTableData());
    }

    /**
     * Pops most recently pushed <code>StateHistory</code> off of the stack
     * using the result to restore the current state history in effect for the
     * state table instance. This method sets the restored history on the
     * history working copy maintained in the event. This method must be
     * overridden when derived classes extend the <code>StateHistory</code> to
     * make sure they update the additional state in the history. Derived class
     * should invoke the no-argument {@link popStateHistory} method to remove
     * the history from the stack casting it to the appropriate derived
     * <code>StateHistory</code> derived class and set the working history on
     * the event.
     * 
     * @param event the event that triggered the transition. The event holds a
     *        reference to the working copy of the state history. This working
     *        copy is updated in this method.
     * 
     * @exception StateExeException thrown when the stack is empty
     */
    public void popStateHistory(final StateEvent event)
        throws StateExeException {
        final StateTableData restore = popStateHistory();

        // Update the history in the working copy. The engine will use this
        // to update the state in the instance
        event.getStateTableData().set(restore);
    }

    /**
     * Returns <code>true</code> if the history stack is empty and
     * <code>false</code> otherwise.
     * 
     * @param event unused
     * 
     * @return <code>true</code> if the history stack is empty and
     *         <code>false</code> otherwise
     */
    public boolean isStackEmpty(StateEvent event) {
        return stack.isEmpty();
    }
}
